try
and catch
are the most important keywords in C++ as far as implementing exception safety goes. To make statements exception safe, you enclose them within a try
block and handle the exceptions that emerge out of the try block in the catch
block.
You use new
to allocate new memory blocks. The most frequently used form of new
returns a pointer to the requested memory if successful or else throws an std::bad_alloc
exception.
In the code below we have defined a method that allocates memory based on the given parameter. The code is exception safe since the new
operator is in a try
block and a catch
mechanism for std::bad_alloc
exception type.
# include <iostream> // std::cout, std::endl
# include <new> // std::bad_alloc
void foo(int size)
{
std::cout << "Start allocating!" << std::endl;
try
{
int* myarray= new int[size];
// Show allocation size
std::cout << "Allocated: "
<< (size*sizeof(int))
<< " bytes" << std::endl;
delete[] myarray;
}
catch (std::bad_alloc& ba)
{
std::cerr << "Exception in foo(" << size << "): "
<< ba.what() << std::endl;
}
std::cout << "All done!" << std::endl;
}
If someone uses the foo(int)
function and gives unusual values as input, then the exception will occur.
int main()
{
foo(5);
foo(-1);
return 0;
}
The output on the console will be:
Start allocating! Allocated: 20 bytes All done! Start allocating! Exception in foo(-1): std::bad_alloc All done!
The first call to out foo(int)
function is executed without encountering any exceptions, but the second call we find there has been an exception. In the absence of the exception
handler, the program would encounter a very ugly end. But thanks to the exception handler, you see that the output displays a decent message. Here is what the console will had looked like if we didn't use exception handling.
Start allocating! Allocated: 20 bytes All done! Start allocating! terminate called after throwing an instance of 'std::bad_alloc' what(): std::bad_alloc Aborted (core dumped)
Whether an exception is caught or not the rest of the function is still executed as we can see that the message "All done!" is shown in both cases. Only when there is a unhandled exception will the execution of the method stop and the program will exit abnormally.
The exception in the example above was thrown from the C++ Standard Library. Such exceptions are of a known type, and catching a particular type is better as you can pin-point the reason for the exception, do better cleanup, or at least show a precise message to the user.
All exceptions thrown by components of the C++ Standard library are described below:
exception | description |
---|---|
bad_alloc | thrown by new on allocation failure |
bad_cast | thrown by dynamic_cast when it fails in a dynamic cast |
bad_exception | thrown by certain dynamic exception specifiers |
bad_typeid | thrown by typeid |
bad_function_call | thrown by empty function objects |
bad_weak_ptr | thrown by shared_ptr when passed a bad weak_ptr |
What happens if a block of code can throw multiple types of exceptions? The exception handling of C++ allows us to use multiple catch
statements to a try
block. The code below shows the use of multiple catch blocks:
# include <iostream> // std::cout, std::endl
# include <vector> // std::vector
# include <stdexcept> // std::out_of_range
// the 1st parameter will determine the size of the container
// the 2nd parameter will specify the value stored in all integers of the container
// the 3rd parameter will specify the location of the integer to return
int foo(int a, int b, int c)
{
std::vector<int> intlist; // declare a vector of integers
intlist.assign (a,b); // the assign method will create new content
// for the vector
// the first argument tells how many integers
// to create and the second argument is the value
// to fill the vector
std::cout << "Assignment successful!" << std::endl;
return intlist.at(c); // return the integer at the given location
}
int main()
{
int integer = 0;
try
{
integer = foo(4, 15, 6);
}
catch(std::bad_alloc& ba)
{
std::cerr << "Bad allocation exception: "
<< ba.what() << std::endl;
}
catch(std::out_of_range& oor)
{
std::cerr << "Out of range exception: "
<< oor.what() << std::endl;
}
std::cout << "Integer value: " << integer << std::endl;
return 0;
}
The output of the code:
Assignment successful! Out of range exception: vector::_M_range_check Integer value: 0
Notice that the appropriate catch block is executed based on the exception type. Also to note is that the value of the integer variable is still 0 at the end of execution. That's because
the execution of the lines in the try
block cease as soon as an exception is thrown, and control will be transferred to the appropriate catch block. Which catch block depends on the type of the object thrown. To better illustrate this behavior, let's see what the output of the console is if the parameters are -1, 15 and 6:
Bad allocation exception: std::bad_alloc Integer value: 0
Now the "Assignment successful!" message is not shown because an exception is caught before the assign
method is finished.
If the values sent to the foo(int, int, int) method were 10, 15, and 6, the output on the console will be:
Assignment succesfull! Integer value: 15
What happens if you don't know the type of exception that will be thrown? There is a general catch handler that will catch any exception. This is usually used when you don't know the type of exception that will be thrown or you don't care about the object that was thrown.
try
{
int* myarray= new int[size];
delete[] myarray;
}
catch ( ... ) // catch any type of exception
{
std::cerr << "Exception caught" << std::endl;
}